﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using EffiProz.Core.Persist;
using EffiProz.Core.Errors;
using EffiProz.Core.Results;
using EffiProz.Core.Lib;
using EffiProz.Core.Lib.IO;
using EffiProz.Core.Navigators;
using EffiProz.Core.DataTypes;

namespace EffiProz.Core
{
    public class SessionData
    {
        private Database database;
        private Session session;
        public PersistentStoreCollectionSession persistentStoreCollection;

        // large results
        LongKeyHashMap resultMap;
        DataFileCacheSession resultCache;

        // VALUE
        public Object currentValue;

        // SEQUENCE
        // SEQUENCE
        public HashMap sequenceMap;
        public OrderedHashSet sequenceUpdateSet;

        public SessionData(Database database, Session session)
        {

            this.database = database;
            this.session = session;
            persistentStoreCollection =
                new PersistentStoreCollectionSession(session);
        }

        // transitional feature
        public PersistentStore getRowStore(TableBase table)
        {

            if (table.store != null)
            {
                return table.store;
            }

            if (table.isSessionBased)
            {
                return persistentStoreCollection.getStore(table);
            }

            return database.persistentStoreCollection.getStore(table);
        }

        public PersistentStore getSubqueryRowStore(TableBase table)
        {

            PersistentStore store = persistentStoreCollection.getStore(table);

            store.removeAll();

            return store;
        }

        public PersistentStore getNewResultRowStore(TableBase table,
                bool isCached)
        {

            try
            {
                PersistentStore store = session.database.logger.newStore(session,
                    persistentStoreCollection, table, isCached);

                return store;
            }
            catch (CoreException ) { }

            throw Error.runtimeError(ErrorCode.U_S0500, "SessionData");
        }

        // result
        public void setResultSetProperties(Result command, Result result)
        {

            if (command.rsConcurrency == ResultConstants.CONCUR_READ_ONLY)
            {
                result.setDataResultConcurrency(ResultConstants.CONCUR_READ_ONLY);
                result.setDataResultHoldability(command.rsHoldability);
            }
            else
            {
                if (result.rsConcurrency == ResultConstants.CONCUR_UPDATABLE)
                {
                    result.setDataResultHoldability(
                        ResultConstants.CLOSE_CURSORS_AT_COMMIT);

                    if (command.rsHoldability
                            != ResultConstants.CLOSE_CURSORS_AT_COMMIT)
                    {
                        session.addWarning(Error.error(ErrorCode.W_36503));
                    }
                }
                else
                {
                    result.setDataResultConcurrency(
                        ResultConstants.CONCUR_READ_ONLY);
                    result.setDataResultHoldability(command.rsHoldability);
                    session.addWarning(Error.error(ErrorCode.W_36502));
                }
            }

            if (command.rsScrollability == ResultConstants.TYPE_SCROLL_SENSITIVE)
            {
                session.addWarning(Error.error(ErrorCode.W_36501));
            }

            result.setDataResultScrollability(command.rsScrollability);
        }

        public Result getDataResultHead(Result command, Result result,
                                 bool isNetwork)
        {

            int fetchSize = command.getFetchSize();

            result.setResultId(session.actionTimestamp);

            if (command.rsConcurrency == ResultConstants.CONCUR_READ_ONLY)
            {
                result.setDataResultConcurrency(ResultConstants.CONCUR_READ_ONLY);
                result.setDataResultHoldability(command.rsHoldability);
            }
            else
            {
                if (result.rsConcurrency == ResultConstants.CONCUR_READ_ONLY)
                {
                    result.setDataResultHoldability(command.rsHoldability);

                    // add warning for concurrency conflict
                }
                else
                {
                    if (session.isAutoCommit())
                    {
                        result.setDataResultConcurrency(
                            ResultConstants.CONCUR_READ_ONLY);
                        result.setDataResultHoldability(
                            ResultConstants.HOLD_CURSORS_OVER_COMMIT);
                    }
                    else
                    {
                        result.setDataResultHoldability(
                            ResultConstants.CLOSE_CURSORS_AT_COMMIT);
                    }
                }
            }

            result.setDataResultScrollability(command.rsScrollability);

            bool hold = false;
            bool copy = false;

            if (result.rsConcurrency == ResultConstants.CONCUR_UPDATABLE)
            {
                hold = true;
            }

            if (isNetwork)
            {
                if (fetchSize != 0
                        && result.getNavigator().getSize() > fetchSize)
                {
                    copy = true;
                    hold = true;
                }
            }
            else
            {
                if (!result.getNavigator().isMemory())
                {
                    hold = true;
                }
            }

            if (hold)
            {
                if (resultMap == null)
                {
                    resultMap = new LongKeyHashMap();
                }

                resultMap.put(result.getResultId(), result);
            }

            if (copy)
            {
                result = Result.newDataHeadResult(session, result, 0, fetchSize);
            }

            return result;
        }

        public Result getDataResultSlice(long id, int offset, int count)
        {

            RowSetNavigatorClient navigator = getRowSetSlice(id, offset, count);

            return Result.newDataRowsResult(navigator);
        }

        public Result getDataResult(long id)
        {

            Result result = (Result)resultMap.get(id);

            return result;
        }

        public RowSetNavigatorClient getRowSetSlice(long id, int offset, int count)
        {

            Result result = (Result)resultMap.get(id);
            RowSetNavigator source = result.getNavigator();

            if (offset + count > source.getSize())
            {
                count = source.getSize() - offset;
            }

            return new RowSetNavigatorClient(source, offset, count);
        }

        public void closeNavigator(long id)
        {

            Result result = (Result)resultMap.remove(id);

            result.getNavigator().close();
        }

        public void closeAllNavigators()
        {

            if (resultMap == null)
            {
                return;
            }

            Iterator it = resultMap.values().iterator();

            while (it.hasNext())
            {
                Result result = (Result)it.next();

                result.getNavigator().close();
            }

            resultMap.clear();
        }

        public void closeAllTransactionNavigators()
        {

            if (resultMap == null)
            {
                return;
            }

            Iterator it = resultMap.values().iterator();

            while (it.hasNext())
            {
                Result result = (Result)it.next();

                if (result.rsHoldability
                        == ResultConstants.CLOSE_CURSORS_AT_COMMIT)
                {
                    result.getNavigator().close();
                    it.remove();
                }
            }

            resultMap.clear();
        }

        public DataFileCacheSession getResultCache()
        {

            if (resultCache == null)
            {
                String path = database.logger.getTempDirectoryPath();

                if (path == null)
                {
                    return null;
                }

                try
                {
                    resultCache =
                        new DataFileCacheSession(database,
                                                 path + "/session_"
                                                 + session.getId());

                    resultCache.open(false);
                }
                catch (Exception )
                {
                    return null;
                }
            }

            return resultCache;
        }

        public void closeResultCache()
        {
            lock (this)
            {
                if (resultCache != null)
                {
                    try
                    {
                        resultCache.close(false);
                    }
                    catch (CoreException) { }

                    resultCache = null;
                }
            }
        }

        // lobs in results
        LongKeyLongValueHashMap resultLobs = new LongKeyLongValueHashMap();

        // lobs in transaction
        LongKeyIntValueHashMap lobUsageCount = new LongKeyIntValueHashMap();
        LongDeque createdLobs = new LongDeque();
        bool hasLobOps;

        // LOBs
        // if rolled back, delete created lobs and ignore all changes
        // if committed,
        // delete created lobs that have no usage count (due to constraint violation or savepoint rollback)
        // update LobManager user counts, delete lobs that have no usage
        public void updateLobUsage(bool commit)
        {

            if (!hasLobOps)
            {
                return;
            }

            hasLobOps = false;

            if (commit)
            {
                for (int i = 0; i < createdLobs.size(); i++)
                {
                    long lobID = createdLobs.get(i);
                    int delta = lobUsageCount.get(lobID, 0);

                    if (delta == 1)
                    {
                        lobUsageCount.remove(lobID);
                        createdLobs.remove(i);

                        i--;
                    }
                    else if (!session.isBatch)
                    {
                        database.lobManager.adjustUsageCount(lobID, delta - 1);
                        lobUsageCount.remove(lobID);
                        createdLobs.remove(i);

                        i--;
                    }
                }

                if (!lobUsageCount.isEmpty())
                {
                    Iterator it = lobUsageCount.keySet().iterator();

                    while (it.hasNext())
                    {
                        long lobID = it.nextLong();
                        int delta = (int)lobUsageCount.get(lobID);

                        database.lobManager.adjustUsageCount(lobID, delta - 1);
                    }

                    lobUsageCount.clear();
                }

                return;
            }
            else
            {
                for (int i = 0; i < createdLobs.size(); i++)
                {
                    long lobID = createdLobs.get(i);

                    database.lobManager.deleteLob(lobID);
                }

                createdLobs.clear();
                lobUsageCount.clear();

                return;
            }
        }

        public void updateLobUsageForBatch()
        {

            for (int i = 0; i < createdLobs.size(); i++)
            {
                long lobID = createdLobs.get(i);

                database.lobManager.deleteLob(lobID);
            }

            createdLobs.clear();
        }

        public void addToCreatedLobs(long lobID)
        {

            hasLobOps = true;

            createdLobs.add(lobID);
        }

        public void addLobUsageCount(long lobID)
        {

            int count = lobUsageCount.get(lobID, 0);

            hasLobOps = true;

            lobUsageCount.put(lobID, count + 1);
        }

        public void removeUsageCount(long lobID)
        {

            int count = lobUsageCount.get(lobID, 0);

            hasLobOps = true;

            lobUsageCount.put(lobID, count - 1);
        }

        /**
         * allocate storage for a new LOB
         */
        public void allocateLobForResult(ResultLob result,
                                         Stream inputStream)
        {

            long resultLobId = result.getLobID();
            CountdownInputStream countStream;

            switch (result.getSubType())
            {

                case LobResultTypes.REQUEST_CREATE_BYTES:
                    {
                        long blobId;
                        long blobLength = result.getBlockLength();

                        if (inputStream == null)
                        {
                            blobId = resultLobId;
                            inputStream = result.getInputStream();
                        }
                        else
                        {
                            BlobData blob = session.createBlob(blobLength);

                            blobId = blob.getId();

                            resultLobs.put(resultLobId, blobId);
                        }

                        countStream = new CountdownInputStream(inputStream);

                        countStream.setCount(blobLength);
                        database.lobManager.setBytesForNewBlob(
                            blobId, countStream, result.getBlockLength());

                        break;
                    }
                case LobResultTypes.REQUEST_CREATE_CHARS:
                    {
                        long clobId;
                        long clobLength = result.getBlockLength();

                        if (inputStream == null)
                        {
                            clobId = resultLobId;

                            if (result.getReader() != null)
                            {
                                inputStream =
                                   new ReaderInputStream( result.getReader() );
                            }
                            else
                            {
                                inputStream = result.getInputStream();
                            }
                        }
                        else
                        {
                            ClobData clob = session.createClob(clobLength);

                            clobId = clob.getId();

                            resultLobs.put(resultLobId, clobId);
                        }

                        countStream = new CountdownInputStream(inputStream);

                        countStream.setCount(clobLength * 2);
                        database.lobManager.setCharsForNewClob(
                            clobId, countStream, result.getBlockLength());

                        break;
                    }
            }
        }

        public void registerLobForResult(Result result)
        {

            RowSetNavigator navigator = result.getNavigator();

            while (navigator.next())
            {
                Object[] data = navigator.getCurrent();

                for (int i = 0; i < data.Length; i++)
                {
                    if (data[i] is BlobData)
                    {
                        BlobData blob = (BlobData)data[i];
                        long id = (long) resultLobs.get((long)blob.getId());

                        data[i] = database.lobManager.getBlob(session, id);
                    }
                    else if (data[i] is ClobData)
                    {
                        ClobData clob = (ClobData)data[i];
                        long id = (long) resultLobs.get((long)clob.getId());

                        data[i] = database.lobManager.getClob(session, id);
                    }
                }
            }

            resultLobs.clear();
            navigator.reset();
        }

        //
        public void startRowProcessing()
        {

            if (sequenceMap != null)
            {
                sequenceMap.clear();
            }
        }

        public Object getSequenceValue(NumberSequence sequence)
        {

            if (sequenceMap == null)
            {
                sequenceMap = new HashMap();
                sequenceUpdateSet = new OrderedHashSet();
            }

            QNameManager.QName key = sequence.getName();
            Object value = sequenceMap.get(key);

            if (value == null)
            {
                value = sequence.getValueObject();

                sequenceMap.put(key, value);
                sequenceUpdateSet.add(sequence);
            }

            return value;
        }

    }
}
